{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Getting to know Julia\n",
    "\n",
    "\n",
    "This notebook is meant to offer a crash course in Julia syntax to show you that Julia is lightweight and easy to use -- like your favorite high-level language!\n",
    "\n",
    "We'll talk about\n",
    "- Strings\n",
    "- Data structures\n",
    "- Loops\n",
    "- Conditionals\n",
    "- Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Strings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "string1 = \"How many cats \""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "string2 = \"is too many cats?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "string(string1, string2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "😺 = 10\n",
    "println(\"I don't know but $😺 are too few!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: Julia allows us to write super generic code, and 😺 is an example of this. \n",
    "\n",
    "This allows us to write code like"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "😺 = 1\n",
    "😀 = 0\n",
    "😞 = -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "😺 + 😞 == 😀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data structures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tuples\n",
    "\n",
    "We can create a tuple by enclosing an ordered collection of elements in `( )`.\n",
    "\n",
    "Syntax: <br>\n",
    "```julia\n",
    "(item1, item2, ...)```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myfavoriteanimals = (\"penguins\", \"cats\", \"sugargliders\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myfavoriteanimals[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dictionaries\n",
    "\n",
    "If we have sets of data related to one another, we may choose to store that data in a dictionary. To do this, we use the `Dict()` function.\n",
    "\n",
    "Syntax:\n",
    "```julia\n",
    "Dict(key1 => value1, key2 => value2, ...)```\n",
    "\n",
    "A good example of a dictionary is a contacts list, where we associate names with phone numbers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myphonebook = Dict(\"Jenny\" => \"867-5309\", \"Ghostbusters\" => \"555-2368\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myphonebook[\"Jenny\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Arrays\n",
    "\n",
    "Unlike tuples, arrays are mutable. Unlike dictionaries, arrays contain ordered sequences of elements. <br>\n",
    "We can create an array by enclosing this sequence of elements in `[ ]`.\n",
    "\n",
    "Syntax: <br>\n",
    "```julia\n",
    "[item1, item2, ...]```\n",
    "\n",
    "\n",
    "For example, we might create an array to keep track of my friends"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myfriends = [\"Ted\", \"Robyn\", \"Barney\", \"Lily\", \"Marshall\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fibonacci = [1, 1, 2, 3, 5, 8, 13]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mixture = [1, 1, 2, 3, \"Ted\", \"Robyn\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also create arrays of other data structures, or multi-dimensional arrays."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numbers = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rand(4, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loops\n",
    "\n",
    "### `for` loops\n",
    "\n",
    "The syntax for a `for` loop is\n",
    "\n",
    "```julia\n",
    "for *var* in *loop iterable*\n",
    "    *loop body*\n",
    "end\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in 1:10\n",
    "    println(n)\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `while` loops\n",
    "\n",
    "The syntax for a `while` is\n",
    "\n",
    "```julia\n",
    "while *condition*\n",
    "    *loop body*\n",
    "end\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 0\n",
    "while n < 10\n",
    "    n += 1\n",
    "    println(n)\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conditionals\n",
    "\n",
    "#### with `if`\n",
    "\n",
    "In Julia, the syntax\n",
    "\n",
    "```julia\n",
    "if *condition 1*\n",
    "    *option 1*\n",
    "elseif *condition 2*\n",
    "    *option 2*\n",
    "else\n",
    "    *option 3*\n",
    "end\n",
    "```\n",
    "\n",
    "allows us to conditionally evaluate one of our options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y = # Enter two numbers here!\n",
    "if x > y\n",
    "    x\n",
    "else\n",
    "    y\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### with ternary operators\n",
    "\n",
    "For this last block, we could instead use the ternary operator with the syntax\n",
    "\n",
    "```julia\n",
    "a ? b : c\n",
    "```\n",
    "\n",
    "which equates to \n",
    "\n",
    "```julia\n",
    "if a\n",
    "    b\n",
    "else\n",
    "    c\n",
    "end\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x > y) ? x : y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Functions\n",
    "\n",
    "Topics:\n",
    "1. How to declare a function\n",
    "2. Duck-typing in Julia\n",
    "3. Mutating vs. non-mutating functions\n",
    "4. Some higher order functions\n",
    "\n",
    "### How to declare a function\n",
    "\n",
    "#### First way: with `function` and `end` keywords"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "function f(x)\n",
    "    x^2\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Second way: with `=`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f2(x) = x^2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Third way: as an anonymous function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f3 = x -> x^2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Calling these functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f2(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f3(42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Duck-typing in Julia\n",
    "*\"If it quacks like a duck, it's a duck.\"* <br><br>\n",
    "Julia functions will just work on whatever inputs make sense. <br><br>\n",
    "For example, `f` will work on a matrix. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = rand(3, 3)\n",
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On the other hand, `f` will not work on a vector. Unlike `A^2`, which is well-defined, the meaning of `v^2` for a vector, `v`, is ambiguous. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v = rand(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mutating vs. non-mutating functions\n",
    "\n",
    "By convention, functions followed by `!` alter their contents and functions lacking `!` do not.\n",
    "\n",
    "For example, let's look at the difference between `sort` and `sort!`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v = [3, 5, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sort(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`sort(v)` returns a sorted array that contains the same elements as `v`, but `v` is left unchanged. <br><br>\n",
    "\n",
    "On the other hand, when we run `sort!(v)`, the contents of v are sorted within the array `v`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sort!(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Some higher order functions\n",
    "\n",
    "#### map\n",
    "\n",
    "`map` is a \"higher-order\" function in Julia that *takes a function* as one of its input arguments. \n",
    "`map` then applies that function to every element of the data structure you pass it. For example, executing\n",
    "\n",
    "```julia\n",
    "map(f, [1, 2, 3])\n",
    "```\n",
    "will give you an output array where the function `f` has been applied to all elements of `[1, 2, 3]`\n",
    "```julia\n",
    "[f(1), f(2), f(3)]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "map(f, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we've squared all the elements of the vector `[1, 2, 3]`, rather than squaring the vector `[1, 2, 3]`.\n",
    "\n",
    "To do this, we could have passed to `map` an anonymous function rather than a named function, such as"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x -> x^3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "via"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "map(x -> x^3, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "and now we've cubed all the elements of `[1, 2, 3]`!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### broadcast\n",
    "\n",
    "`broadcast` is another higher-order function like `map`. `broadcast` is a generalization of `map`, so it can do every thing `map` can do and more. The syntax for calling `broadcast` is the same as for calling `map`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "broadcast(f, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and again, we've applied `f` (squared) to all the elements of `[1, 2, 3]` - this time by \"broadcasting\" `f`!\n",
    "\n",
    "Some syntactic sugar for calling `broadcast` is to place a `.` between the name of the function you want to `broadcast` and its input arguments. For example,\n",
    "\n",
    "```julia\n",
    "broadcast(f, [1, 2, 3])\n",
    "```\n",
    "is the same as\n",
    "```julia\n",
    "f.([1, 2, 3])\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f.([1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice again how different this is from calling \n",
    "```julia\n",
    "f([1, 2, 3])\n",
    "```\n",
    "We can square every element of a vector, but we can't square a vector!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To drive home the point, let's look at the difference between\n",
    "\n",
    "```julia\n",
    "f(A)\n",
    "```\n",
    "and\n",
    "```julia\n",
    "f.(A)\n",
    "```\n",
    "for a matrix `A`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = [i + 3*j for j in 0:2, i in 1:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As before we see that for a matrix, `A`,\n",
    "```\n",
    "f(A) = A^2 = A * A\n",
    "``` \n",
    "\n",
    "On the other hand,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = f.(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "contains the squares of all the entries of `A`.\n",
    "\n",
    "This dot syntax for broadcasting allows us to write relatively complex compound elementwise expressions in a way that looks natural/closer to mathematical notation. For example, we can write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "C = A .+ 2 .* f.(A) ./ A"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "instead of"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "broadcast(x -> x + 2 * f(x) / x, A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and this will still compile down to code that runs as efficiently as `C`!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 1.0.0",
   "language": "julia",
   "name": "julia-1.0"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.0.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
